package com.yexx.advisor;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Description: 对AdvisorDemo中的method进行增强
 *
 * @author: zuomin (myleszelic@outlook.com)
 * @date: 2020/10/21-13:27
 */
public class AdvisorTest extends ClassLoader {

    public static void main(String[] args) throws Exception {

        ClassPool classPool = ClassPool.getDefault();

        ClassClassPath classClassPath1 = new ClassClassPath(AdvisorTest.class);
        ClassClassPath classClassPath2 = new ClassClassPath(AdvisorDemo.class);
        classPool.insertClassPath(classClassPath1);
        classPool.insertClassPath(classClassPath2);

        CtClass ctClass = classPool.get(AdvisorDemo.class.getName());
//        ctClass.replaceClassName("AdvisorTest", "AdvisorTest02");

        CtMethod method = ctClass.getDeclaredMethod("strToInt");

        //获取方法的信息
        MethodInfo methodInfo = method.getMethodInfo();

        //是否为静态方法,影响取值,static方法第一个参数是 this
        boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) != 0;

        //获取出入参名称
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        //获取方法入参类型
        CtClass[] parameterTypes = method.getParameterTypes();
        //获取出参类型
        CtClass returnType = method.getReturnType();
        String returnTypeName = returnType.getName();

//        System.out.println("类名：" + ctClass.getName());
//        System.out.println("方法：" + method.getName());
//        System.out.println("类型：" + (isStatic ? "静态方法" : "非静态方法"));
//        System.out.println("描述：" + methodInfo.getDescriptor());
//        System.out.println("入参[名称]：" + localVariableAttribute.variableName(1) + "," + localVariableAttribute.variableName(2));
//        System.out.println("入参[类型]：" + parameterTypes[0].getName() + "," + parameterTypes[1].getName());
//        System.out.println("出参[类型]：" + returnTypeName);


        //++++++++++++++++++++++++++++++++++++ Monitor增强


        int parameterSize = isStatic ? localVariableAttribute.tableLength() : localVariableAttribute.tableLength() - 1; // 静态类型取值
        List<String> parameterNameList = new ArrayList<>(parameterSize);            // 入参名称
        List<String> parameterTypeList = new ArrayList<>(parameterSize);            // 入参类型
        StringBuilder parameters = new StringBuilder();                             // 参数组装；$1、$2...，$$可以获取全部，但是不能放到数组初始化

        for (int i = 0; i < parameterSize; i++) {
            parameterNameList.add(localVariableAttribute.variableName(i + (isStatic ? 0 : 1))); // 静态类型去掉第一个this参数
            parameterTypeList.add(parameterTypes[i].getName());
            if (i + 1 == parameterSize) {
                parameters.append("$").append(i + 1);
            } else {
                parameters.append("$").append(i + 1).append(",");
            }
        }

        // 方法：生成方法唯一标识ID
        int idx = Monitor.generateMethodId(ctClass.getName(), method.getName(), parameterNameList, parameterTypeList, returnTypeName);

        //方法增强
        method.addLocalVariable("startNanos", CtClass.longType);
        //封装类型 需要使用classPool处理,javassit不会处理封装类型
        method.addLocalVariable("parameterValues", classPool.get(Objects[].class.getName()));

        //方法前增强 : 插入毫秒值, 入参列表($1,$2)使用变量
        long nanoTime = System.nanoTime();
        method.insertBefore("{startNanos = System.nanoTime();\n parameterValues = new Object[]{" + parameters.toString() + "};}");

        //方法后增强 : 如果返回类型非对象类型，$_ 需要进行类型转换
        method.insertAfter("{com.yexx.advisor.Monitor.point(" + idx + ", startNanos, parameterValues, $_);}", false);

        // 方法；添加TryCatch 添加异常捕获
        method.addCatch("{com.yexx.advisor.Monitor.point(" + idx + ", $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception"));

        ctClass.writeFile();

        //test
        byte[] bytes = ctClass.toBytecode();
        Class<?> aClass = new AdvisorTest().defineClass("com.yexx.advisor.AdvisorDemo", bytes, 0, bytes.length);

        //反射获取方法
        Method method1 = aClass.getDeclaredMethod("strToInt", String.class, String.class);
        Object obj_01 = method1.invoke(aClass.getDeclaredConstructor().newInstance(), "1", "2");
        System.out.println("正确入参：" + obj_01);

        Object obj_02 = method1.invoke(aClass.getDeclaredConstructor().newInstance(), "a", "b");
        System.out.println("异常入参：" + obj_02);


    }
}
